/**
 * @fileoverview
 * 
 * sensor to measure navigation performance
 * if there is no data provided with http://www.w3.org/TR/navigation-timing/
 * as fallback provides info about last unload time and load time events
 * in timing fields:
 * - legacyStart (measurement when this script is loaded and executed)
 * - legacyEnd (measurement within onload event)
 * - legacyUnload (measurement of previous unload event if localStorage is available)
 *
 * in addition to measurements sensor provide information about current location and referrer
 * 
 * @author Sebastian Brzuzek sebastian@brzuzek.pl
 */

goog.provide('uxperf.profiler.SensorNavigation');
goog.provide('uxperf.profiler.BufferItemNavigation');

goog.require('uxperf.profiler.SensorBase');
goog.require('uxperf.BufferItem');
goog.require('uxperf.Storage');
goog.require('goog.array');

/**
 * Navigation sensor implementation
 *
 * @param {number} index number of sensor in configuration structure
 * @param {uxperf.Queue} opt_queue optional queue to store events (global queue instance for default)
 * 
 * @constructor
 * @extends {uxperf.profiler.SensorBase}
 */
uxperf.profiler.SensorNavigation = function(index, opt_queue) {
	goog.base(this, index, uxperf.profiler.SensorNavigation.DEFAULT_CONFIG, opt_queue);

	/**
	 * storage name for last unload time value
	 * @type {string}
	 */
	this.storageValueName_ = /** @type {string} */ (this.getParam(uxperf.profiler.SensorNavigation.CONFIG.LAST_UNLOAD_STORAGE_NAME));
  if (goog.DEBUG) LOG.info('SensorNavigation - construction done');
};
goog.inherits(uxperf.profiler.SensorNavigation, uxperf.profiler.SensorBase);

/**
 * get start time as soon as posible
 * for use with legacy browsers
 * 
 * @type {number}
 */
uxperf.profiler.SensorNavigation.START_TIME = goog.now();


/**
 * define placeholder for end time
 * for use with legacy browsers
 * 
 * @type {number}
 */
uxperf.profiler.SensorNavigation.ONLOAD_TIME = -1;

/**
 * flag if we are on legacy browser without support
 * for windows.performance
 *
 * @type {boolean}
 */
uxperf.profiler.SensorNavigation.LEGACY_MODE = true;

/**
 * storage implementation used for legacy browsers to store on unload time
 * @type {uxperf.Storage}
 */
uxperf.profiler.SensorNavigation.prototype.storage_ = null;

/**
 * Initialization of sensor
 *
 * @return {uxperf.profiler.SensorNavigation} this sensor instance
 * @override
 */
uxperf.profiler.SensorNavigation.prototype.init = function() {
  if (goog.DEBUG) LOG.info('SensorNavigation - initialization');
	if (this.isReady()) return this;
	try {
		this.storage_ = uxperf.Storage.getInstance();
		var sensorNavigation = this;
    if (goog.DEBUG) LOG.info('SensorNavigation - setup listener for document load event');
		//listen for onload event
		goog.events.listen(window, goog.events.EventType.LOAD, function () {
        uxperf.profiler.SensorNavigation.ONLOAD_TIME = goog.now();
        if (goog.DEBUG) LOG.info('SensorNavigation - handling load event - onload time: ' + uxperf.profiler.SensorNavigation.ONLOAD_TIME);
        //gather Navigation performance info few milliseconds after onload
        if (goog.DEBUG) LOG.info('SensorNavigation - schedule deferred collecting of navigation performance info');
        setTimeout(function () {
            if (goog.DEBUG) LOG.info('SensorNavigation - collecting performance info by invoking handleEvent');
            sensorNavigation.handleEvent({'type': 'Navigation', 'target': window});
            uxperf.profiler.SensorNavigation.LEGACY_MODE = !(window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance);
            if (uxperf.profiler.SensorNavigation.LEGACY_MODE) {
              if (goog.DEBUG) LOG.info('SensorNavigation - we are running in legacy mode, register listener for unload event');
              goog.events.listen(window, goog.events.EventType.UNLOAD, function() {
                  if (goog.DEBUG) LOG.info('SensorNavigation - handling unload event');
                  if (this.storage_) this.storage_.set(this.storageValueName_, goog.now());
                },
                true,
                sensorNavigation
              );
            }
          },
          1
        );
      },
      true,
      this
    );
	} catch (e) {
		this.errorNotify(e);
	}

	this.readyNotify();
	return this;
};

/** @const list of fields in Performance.Navigation object */
uxperf.profiler.SensorNavigation.PERFORMANCE_NAVIGATION = [
	'type',
	'redirectCount'
];

/** @const list of fields in Performance.Timing object */
uxperf.profiler.SensorNavigation.PERFORMANCE_TIMING = [
	//legacy timing fields
	'legacyStart',
	'legacyEnd',
	'legacyUnload',
	//PerformanceTiming interface fields defined in http://www.w3.org/TR/navigation-timing/
	'navigationStart',
  'unloadEventStart',
  'unloadEventEnd',
  'redirectStart',
  'redirectEnd',
  'fetchStart',
  'domainLookupStart',
  'domainLookupEnd',
  'connectStart',
  'connectEnd',
  'secureConnectionStart',
  'requestStart',
  'responseStart',
  'responseEnd',
  'domLoading',
  'domInteractive',
  'domContentLoadedEventStart',
  'domContentLoadedEventEnd',
  'domComplete',
  'loadEventStart',
  'loadEventEnd'
];

/**
 * @inheritDoc
 */
uxperf.profiler.SensorNavigation.prototype.convert = function (event) {
  if (goog.DEBUG) LOG.info('SensorNavigation - convert event to navigation performance item');
	//cross browser supports http://www.w3.org/TR/navigation-timing/
	var perf = window.performance || window.mozPerformance || window.msPerformance || window.webkitPerformance || {};
	if (!perf['timing']) {
		perf['timing'] = {
			'legacyStart': uxperf.profiler.SensorNavigation.START_TIME,
			'legacyEnd': uxperf.profiler.SensorNavigation.ONLOAD_TIME
		};
		
		if (this.storage_) {
			perf['timing']['legacyUnload'] = this.storage_.get(this.storageValueName_) || false;
		}
	}

  if (goog.DEBUG) LOG.info('SensorNavigation - call sensor base conversion');
  return uxperf.profiler.SensorNavigation.superClass_.convert.call(this, event, new uxperf.BufferItemNavigation(perf, this));
};

/**
 * constants for config parameters
 * 
 * @enum {string}
 */
uxperf.profiler.SensorNavigation.CONFIG = {
	LAST_UNLOAD_STORAGE_NAME: 'last_unload_storage_name'
};

/** @const sensor type name */
uxperf.profiler.SensorNavigation.TYPE_NAME = 'navigation_performance';

/**
 * Default configuration
 */
uxperf.profiler.SensorNavigation.DEFAULT_CONFIG = {};
uxperf.profiler.SensorNavigation.DEFAULT_CONFIG[uxperf.profiler.SensorBase.CONFIG.TYPE_KEY] = uxperf.profiler.SensorNavigation.TYPE_NAME;
uxperf.profiler.SensorNavigation.DEFAULT_CONFIG[uxperf.profiler.SensorBase.CONFIG.AUTOINIT_KEY] = uxperf.FactoryBase.AUTOINIT_MODE.ON_CONSTRUCTION;
uxperf.profiler.SensorNavigation.DEFAULT_CONFIG[uxperf.profiler.SensorNavigation.CONFIG.LAST_UNLOAD_STORAGE_NAME] = 'last_unload';

/**
 * Buffer Item related to navigation performance events
 *
 * @param {!Object} perf structure with performance data
 * @param {!uxperf.profiler.SensorNavigation} sensor source of data
 * 
 * @constructor
 * @extends {uxperf.BufferItemSensor}
 */
uxperf.BufferItemNavigation = function(perf, sensor) {
	goog.base(this, sensor);
	
  /**
   * Navigation performance info
   * @type {Object}
   */
	this.perf_ = perf;
};
goog.inherits(uxperf.BufferItemNavigation, uxperf.BufferItemSensor);

/** @inheritDoc */
uxperf.BufferItemNavigation.prototype.serialize = function() {
	/** @type {Object} */
	var res = uxperf.BufferItemNavigation.superClass_.serialize.call(this);

 	/** @type {uxperf.BufferItemNavigation} */
	var buf = this;

	res['timing'] = {};
	goog.array.forEach(uxperf.profiler.SensorNavigation.PERFORMANCE_TIMING, function (el) {
		if (buf.perf_['timing'][el]) res['timing'][el] = buf.perf_['timing'][el];
	});
	res['navigation'] = {}
	goog.array.forEach(uxperf.profiler.SensorNavigation.PERFORMANCE_NAVIGATION, function (el) {
		if (buf.perf_['navigation'][el]) res['navigation'][el] = buf.perf_['navigation'][el];
	});
  res['location'] = window.location.href;
  res['referrer'] = window.document.referrer;

	return res;
};

/*
http://www.w3.org/TR/navigation-timing/

should be available in general with name:

	window.performance

in some browser versions available with prefixed names:
	window.mozPerformance
	window.msPerformance
	window.webkitPerformance

interface definitions:

interface PerformanceTiming {
  readonly attribute unsigned long long navigationStart;
  readonly attribute unsigned long long unloadEventStart;
  readonly attribute unsigned long long unloadEventEnd;
  readonly attribute unsigned long long redirectStart;
  readonly attribute unsigned long long redirectEnd;
  readonly attribute unsigned long long fetchStart;
  readonly attribute unsigned long long domainLookupStart;
  readonly attribute unsigned long long domainLookupEnd;
  readonly attribute unsigned long long connectStart;
  readonly attribute unsigned long long connectEnd;
  readonly attribute unsigned long long secureConnectionStart;
  readonly attribute unsigned long long requestStart;
  readonly attribute unsigned long long responseStart;
  readonly attribute unsigned long long responseEnd;
  readonly attribute unsigned long long domLoading;
  readonly attribute unsigned long long domInteractive;
  readonly attribute unsigned long long domContentLoadedEventStart;
  readonly attribute unsigned long long domContentLoadedEventEnd;
  readonly attribute unsigned long long domComplete;
  readonly attribute unsigned long long loadEventStart;
  readonly attribute unsigned long long loadEventEnd;
};

interface PerformanceNavigation {
  const unsigned short TYPE_NAVIGATE = 0;
  const unsigned short TYPE_RELOAD = 1;
  const unsigned short TYPE_BACK_FORWARD = 2;
  const unsigned short TYPE_RESERVED = 255;
  readonly attribute unsigned short type;
  readonly attribute unsigned short redirectCount;
};

interface Performance {
  readonly attribute PerformanceTiming timing;
  readonly attribute PerformanceNavigation navigation;
};

partial interface Window {
  [Replaceable] readonly attribute Performance performance;
};
*/
